Spring Security | Note-14

Spring Security Note-14


Spring Security OAuth核心源码

接下来,通过源码解析的方式,我们去了解如何将之前开发的

用户名密码登录方式

手机短信登录方式

社交帐号登录方式

以上三种自定义的认证方式,嫁接到Spring Security OAuth认证服务器中,并且实现Token的生成存储;

-

步骤略解

/oauth/token的请求会被TokenEndpoint拦截,通过ClientDetailsService获取ClientDetails(第三方相信),并一起封装在TokenRequest(请求的信息)中;

用这个TokenRequest会去调用一个TokenGranter的接口,在这后面,封装的是四种授权方式不同的实现,在这个接口里面,会根据你传入的grant_type具体选择一个Token的生成逻辑;

生成的逻辑,都会产生两个东西OAuth2Request(是之前两个信息ClientDetailsTokenRequest的整合)和Authentication

这两个对象组合起来会生成一个叫OAuth2Authentication的对象,最终产生的OAuth2Authentication对象这个包含哪个第三方应用,请求哪个用户授权,授权模式,授权参数等所有的信息;

这个OAuth2Authentication对象会传入AuthorizationServerTokenServices接口的实现,最终生成一个OAuth2AccessToken令牌;

TokenStore实现令牌的存取;

TokenEnhancer实现令牌的增强;

-

密码模式

首先进入到TokenEndpoint中,处理/oauth/token的POST请求,拿到的第三方信息,根据第三方信息去创建一个TokenRequest;

拿到TokenRequest之后,去进行一系列的判断,最终会传到TokenGranter的grant当中产生最终的Token;

CompositeTokenGranter
1
2
3
4
5
6
7
8
9
10
// 四种授权模式 & 刷新令牌的模式根据grant_type判断
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
for (TokenGranter granter : tokenGranters) {
OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
if (grant!=null) {
return grant;
}
}
return null;
}
AbstractTokenGranter

getAccessToken产生最终的Token;

1
2
3
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
}
DefaultTokenServices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
// 从tokenStore获取OAuth2AccessToken(如果令牌存在,不同的授权模式下将返回同一个令牌)
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
// 判断是否过期
if (existingAccessToken != null) {
if (existingAccessToken.isExpired()) {
if (existingAccessToken.getRefreshToken() != null) {
// 删除过期的令牌
refreshToken = existingAccessToken.getRefreshToken();
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
}
else {
// 如果令牌存在则重新存储
tokenStore.storeAccessToken(existingAccessToken, authentication);
// 存储后直接返回
return existingAccessToken;
}
}
// 判断刷新令牌不存在
if (refreshToken == null) {
// 创建刷新令牌
refreshToken = createRefreshToken(authentication);
}
else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
// 过期
ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
refreshToken = createRefreshToken(authentication);
}
}
// 根据刷新令牌创建OAuth2AccessToken
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
tokenStore.storeRefreshToken(refreshToken, authentication);
}
//返 回OAuth2AccessToken
return accessToken;
}

重构用户名密码登录

我们通过登录请求,到自定义的Filter重新到登录逻辑进行处理,在自定义的AuthenticationSuccessHandler处理之后,再获得OAuth2Request和Authentication,再达到最后的Token的获取;

通过请求参数,获取CilentId,根据ClientId通过ClientDetailsService获取ClientDetails,让ClientDetails和TokenRequest一同封装成OAuth2Request;

最终目标是得到OAuth2Request对象;

-

改造Token的生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@Component("imoocAuthenticationSuccessHandler")
public class ImoocAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices;

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
logger.info("登录成功");
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Basic ")) {
throw new UnapprovedClientAuthenticationException("请求头中无CLIENT信息");
}
// 解码获取ClientId
String[] tokens = extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String clientId = tokens[0];
String clientSecret = tokens[1];
// 获取ClientDetails
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if (clientDetails == null) {
throw new UnapprovedClientAuthenticationException("CLIENT_ID对应配置信息不存在:" + clientId);
} else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
throw new UnapprovedClientAuthenticationException("CLIENT_SECRET信息不匹配:" + clientSecret);
}
// TokenRequest
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");
// OAuth2Request
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
// 用OAuth2Request和Authentication封装成OAuth2Authentication
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
// 传给AuthorizationServerTokenServices
OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(token));
}

private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.decode(base64Token);
} catch (IllegalArgumentException e) {
throw new BadCredentialsException("Failed to decode basic authentication token");
}
String token = new String(decoded, "UTF-8");
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
}
return new String[]{token.substring(0, delim), token.substring(delim + 1)};
}
}

改造完成,我们还需要在安全配置类中去配置,保护我们的资源安全;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Configuration
@EnableResourceServer
public class ImoocResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Autowired
private SpringSocialConfigurer imoocSocialSecurityConfig;

@Override
public void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
.loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
.successHandler(imoocAuthenticationSuccessHandler)
.failureHandler(imoocAuthenticationFailureHandler);
http
// .apply(validateCodeSecurityConfig)
// .and()
.apply(smsCodeAuthenticationSecurityConfig)
.and()
.apply(imoocSocialSecurityConfig)
.and()
.authorizeRequests()
// 当访问以下URL,不需要身份认证
.antMatchers(
SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*",
securityProperties.getBrowser().getLoginPage(),
securityProperties.getBrowser().getSignUpUrl(),
securityProperties.getBrowser().getSession().getSessionInvalidUrl(),
securityProperties.getBrowser().getSignOutUrl(),
"/user/regist"
).permitAll()
// 任何请求
.anyRequest()
// 认证后才能访问
.authenticated().and().csrf().disable();
}
}

启动项目测试,做一个登陆的请求,携带用户名和密码并携带:authentication(封装clientId和clientSecret)的信息,因为项目的用户名密码的登录请求路径是authentication/form;


重构短信登录

问题

上部分在用户名密码的登录方式中,我们用Token的方式已经重新实现了,但是依然有许多的问题;

短信验证码登录的逻辑,设计到验证码存储的逻辑,

在这之前,我们是通过浏览器去发起请求,然后在服务器中生成,存储在SESSION当中的;

在之后的请求当中,再对验证码进行校验;

-

解决

在APP的架构下,服务器是不存在SESSION的,那么验证码是无处可放的;

解决的思路,我们是在APP发起请求时,在生成和校验验证码逻辑的过程中,加上deviceId(设备ID),根据你的deviceId放到外部存储当中,进行生成和校验;

我们需要重构验证码的相关代码;

1
2
3
4
5
public interface ValidateCodeProcessor {
String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_";
void create(ServletWebRequest request) throws Exception;
void validate(ServletWebRequest servletWebRequest);
}
1
2
3
4
5
6
7
8
public interface ValidateCodeRepository {
// 保存验证码
void save(ServletWebRequest request,ValidateCode code,ValidateCodeType validateCodeType);
// 获取验证码
ValidateCode get(ServletWebRequest request,ValidateCodeType validateCodeType);
// 删除验证码
void remove(ServletWebRequest request,ValidateCodeType validateCodeType);
}

在APP项目中的验证码逻辑;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Component
public class RedisValidateCodeRepository implements ValidateCodeRepository {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;

@Override
public void save(ServletWebRequest request, ValidateCode code, ValidateCodeType type) {
redisTemplate.opsForValue().set(buildKey(request, type), code, 30, TimeUnit.MINUTES);
}

@Override
public ValidateCode get(ServletWebRequest request, ValidateCodeType type) {
Object value = redisTemplate.opsForValue().get(buildKey(request, type));
if (value == null) {
return null;
}
return (ValidateCode) value;
}

@Override
public void remove(ServletWebRequest request, ValidateCodeType type) {
redisTemplate.delete(buildKey(request, type));
}

private String buildKey(ServletWebRequest request, ValidateCodeType type) {
String deviceId = request.getHeader("deviceId");
if (StringUtils.isBlank(deviceId)) {
throw new ValidateCodeException("请在请求头中携带deviceId参数");
}
return "code:" + type.toString().toLowerCase() + ":" + deviceId;
}
}

在Browser项目中的验证码逻辑;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Component
public class SessionValidateCodeRepository implements ValidateCodeRepository {
String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_";
/**
* 操作session的工具类
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

@Override
public void save(ServletWebRequest request, ValidateCode code, ValidateCodeType validateCodeType) {
sessionStrategy.setAttribute(request, getSessionKey(request, validateCodeType), code);
}
/**
* 构建验证码放入session时的key
*/
private String getSessionKey(ServletWebRequest request, ValidateCodeType validateCodeType) {
return SESSION_KEY_PREFIX + validateCodeType.toString().toUpperCase();
}
@Override
public ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType) {
return (ValidateCode) sessionStrategy.getAttribute(request, getSessionKey(request, validateCodeType));
}

@Override
public void remove(ServletWebRequest request, ValidateCodeType codeType) {
sessionStrategy.removeAttribute(request, getSessionKey(request, codeType));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {
/**
* 收集系统中所有的 {@link ValidateCodeGenerator} 接口的实现
*/
@Autowired
private Map<String, ValidateCodeGenerator> validateCodeGenerators;
@Autowired
private ValidateCodeRepository validateCodeRepository;

@Override
public void create(ServletWebRequest request) throws Exception {
C validateCode = generate(request);
save(request, validateCode);
send(request, validateCode);
}

/**
* 生成校验码
*/
@SuppressWarnings("unchecked")
private C generate(ServletWebRequest request) {
String type = getValidateCodeType(request).toString().toLowerCase();
String generatorName = type + ValidateCodeGenerator.class.getSimpleName();
ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(generatorName);
if (validateCodeGenerator == null) {
throw new ValidateCodeException("验证码生成器" + generatorName + "不存在");
}
return (C) validateCodeGenerator.generate(request);
}

/**
* 保存校验码
*/
private void save(ServletWebRequest request, C validateCode) {
ValidateCode code = new ValidateCode(validateCode.getCode(), validateCode.getExpireTime());
validateCodeRepository.save(request, code, getValidateCodeType(request));
}

/**
* 发送校验码,由子类实现
*/
protected abstract void send(ServletWebRequest request, C validateCode) throws Exception;

/**
* 根据请求的url获取校验码的类型
*/
private ValidateCodeType getValidateCodeType(ServletWebRequest request) {
String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor");
return ValidateCodeType.valueOf(type.toUpperCase());
}

@SuppressWarnings("unchecked")
@Override
public void validate(ServletWebRequest request) {
ValidateCodeType codeType = getValidateCodeType(request);
C codeInSession = (C) validateCodeRepository.get(request, codeType);

String codeInRequest;
try {
codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), codeType.getParamNameOnValidate());
} catch (ServletRequestBindingException e) {
throw new ValidateCodeException("获取验证码失败");
}

if (StringUtils.isBlank(codeInRequest)) {
throw new ValidateCodeException(codeType + "验证码不能为空");
}
if (codeInSession == null) {
throw new ValidateCodeException(codeType + "验证码不存在");
}
if (codeInSession.isExpired()) {
validateCodeRepository.remove(request, codeType);
throw new ValidateCodeException(codeType + "验证码已过期");
}

if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
throw new ValidateCodeException(codeType + "验证码不匹配");
}

validateCodeRepository.remove(request, codeType);
}

}

测试

向手机:13012345678. 发送短信验证码801409

-

-

-

在工具里发送的验证码登录请求,实际上以上是带上Cookies的HTTP请求,所以我们需要通过一段CMD代码,转化为APP的请求;

1
2
3
4
5
6
7
curl -X POST \
http://127.0.0.1:8060/authentication/mobile \
-H 'authorization: Basic aW1vb2M6aW1vb2NzZWNyZXQ=' \
-H 'cache-control: no-cache' \
-H 'content-type: application/x-www-form-urlencoded' \
-H 'deviceid: 007' \
-H 'postman-token: 3646c210-6b71-316d-f1a2-f7cf91a242a6'

重构社交登录

简化模式

用户直接访问app,在app里面点击QQ或微信的第三方登录,QQ或微信返回openId和accessToken,app用openId换取令牌,然后返回令牌;

封装请求信息TokenOpenIdAuthenticationToken
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {

private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

private final Object principal;
private String providerId;

public OpenIdAuthenticationToken(String openId, String providerId) {
super(null);
this.principal = openId;
this.providerId = providerId;
setAuthenticated(false);
}

public OpenIdAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true); // must use super, as we override
}

public Object getCredentials() {
return null;
}

public Object getPrincipal() {
return this.principal;
}

public String getProviderId() {
return providerId;
}

public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}

@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}

需要一个过滤器,拦截登录请求,然后把信息封装,交到OpenIdAuthenticationToken里面;

-

拦截请求OpenIdAuthenticationFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface SecurityConstants {
/**
* openid参数名
*/
String DEFAULT_PARAMETER_NAME_OPENID = "openId";
/**
* providerId参数名
*/
String DEFAULT_PARAMETER_NAME_PROVIDERID = "providerId";
/**
* 默认的OPENID登录请求处理url
*/
String DEFAULT_LOGIN_PROCESSING_URL_OPENID = "/authentication/openid";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class OpenIdAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private String openIdParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_OPENID;
private String providerIdParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_PROVIDERID;
private boolean postOnly = true;

public OpenIdAuthenticationFilter() {
super(new AntPathRequestMatcher(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_OPENID, "POST"));
}

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String openid = obtainOpenId(request);
String providerId = obtainProviderId(request);
if (openid == null) {
openid = "";
}
if (providerId == null) {
providerId = "";
}
openid = openid.trim();
providerId = providerId.trim();
OpenIdAuthenticationToken authRequest = new OpenIdAuthenticationToken(openid, providerId);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* 获取openId
*/
protected String obtainOpenId(HttpServletRequest request) {
return request.getParameter(openIdParameter);
}
/**
* 获取提供商id
*/
protected String obtainProviderId(HttpServletRequest request) {
return request.getParameter(providerIdParameter);
}

protected void setDetails(HttpServletRequest request, OpenIdAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}

public void setOpenIdParameter(String openIdParameter) {
Assert.hasText(openIdParameter, "Username parameter must not be empty or null");
this.openIdParameter = openIdParameter;
}

public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}

public final String getOpenIdParameter() {
return openIdParameter;
}

public String getProviderIdParameter() {
return providerIdParameter;
}

public void setProviderIdParameter(String providerIdParameter) {
this.providerIdParameter = providerIdParameter;
}
}
验证服务器OpenIdAuthenticationProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class OpenIdAuthenticationProvider implements AuthenticationProvider {
private SocialUserDetailsService userDetailsService;

private UsersConnectionRepository usersConnectionRepository;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

OpenIdAuthenticationToken authenticationToken = (OpenIdAuthenticationToken) authentication;

Set<String> providerUserIds = new HashSet<>();
providerUserIds.add((String) authenticationToken.getPrincipal());
Set<String> userIds = usersConnectionRepository.findUserIdsConnectedTo(authenticationToken.getProviderId(), providerUserIds);

if(CollectionUtils.isEmpty(userIds) || userIds.size() != 1) {
throw new InternalAuthenticationServiceException("无法获取用户信息");
}

String userId = userIds.iterator().next();

UserDetails user = userDetailsService.loadUserByUserId(userId);

if (user == null) {
throw new InternalAuthenticationServiceException("无法获取用户信息");
}

OpenIdAuthenticationToken authenticationResult = new OpenIdAuthenticationToken(user, user.getAuthorities());

authenticationResult.setDetails(authenticationToken.getDetails());

return authenticationResult;
}

@Override
public boolean supports(Class<?> authentication) {
return OpenIdAuthenticationToken.class.isAssignableFrom(authentication);
}
}
安全配置类OpenIdAuthenticationSecurityConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Component
public class OpenIdAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
@Autowired
private SocialUserDetailsService userDetailsService;
@Autowired
private UsersConnectionRepository usersConnectionRepository;

@Override
public void configure(HttpSecurity http) throws Exception {

OpenIdAuthenticationFilter OpenIdAuthenticationFilter = new OpenIdAuthenticationFilter();
OpenIdAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
OpenIdAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);
OpenIdAuthenticationFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);

OpenIdAuthenticationProvider OpenIdAuthenticationProvider = new OpenIdAuthenticationProvider();
OpenIdAuthenticationProvider.setUserDetailsService(userDetailsService);
OpenIdAuthenticationProvider.setUsersConnectionRepository(usersConnectionRepository);

http.authenticationProvider(OpenIdAuthenticationProvider).addFilterAfter(OpenIdAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
资源服务器配置ImoocResourceServerConfig

将刚刚写好的OpenId安全配置类加入到配置当中;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@EnableResourceServer
public class ImoocResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private OpenIdAuthenticationSecurityConfig openIdAuthenticationSecurityConfig;
@Override
public void configure(HttpSecurity http) throws Exception {
...
http.
...
.apply(openIdAuthenticationSecurityConfig)
.and()
...
}
}

发送请求,通过OpenId获取信息,成功登录;


标准授权码模式

标准的授权码模式,APP请求QQ或微信去获取授权码;

然后将授权码交给我们自己的第三方client,由第三方client带着授权码去QQ;

或微信申请令牌,然后发放令牌给第三方应用,然后读取用户数据;

然后第三方应用重新生成自己的令牌返回给APP;

在此处换令牌之前获取授权码,停止服务;

http://xxx/qqLogin/weixin

?code=061f8yj728JKBJ0DADl72sMEj72f8yjh

&state=344fadca-77e7-47a4-a3cd-7cd988f1953d

1
2
3
public interface SocialAuthenticationFilterPostProcessor {
void process(SocialAuthenticationFilter socialAuthenticationFilter);
}
1
2
3
4
5
6
7
8
9
10
11
public class ImoocSpringSocialConfigurer extends SpringSocialConfigurer {
private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;
@Override
protected <T> T postProcess(T object) {
...
if(socialAuthenticationFilterPostProcessor != null){
socialAuthenticationFilterPostProcessor.process(filter);
}
return (T) filter;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Order(1)
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
@Autowired(required = false)
private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;

// 将SpringSocialFilter添加到安全配置的Bean
@Bean
public SpringSocialConfigurer imoocSocialSecurityConfig() {
... configurer.setSocialAuthenticationFilterPostProcessor(socialAuthenticationFilterPostProcessor);
return configurer;
}
}

将成功处理器设置进来imoocAuthenticationSuccessHandler

1
2
3
4
5
6
7
8
9
10
@Component
public class AppSocialAuthenticationFilterPostProcessor implements SocialAuthenticationFilterPostProcessor {
@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;

@Override
public void process(SocialAuthenticationFilter socialAuthenticationFilter) {
socialAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);
}
}

完成返回第三方应用生成的Token给APP;